/*
 * Copyright 2019 Flipkart Internet Pvt. Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.flipkart.ohos.proteus;

import ohos.agp.components.Component;
import ohos.agp.components.ComponentContainer;
import ohos.javax.xml.stream.XMLStreamException;
import ohos.javax.xml.stream.XMLStreamReader;

import com.flipkart.ohos.proteus.managers.ViewManager;
import com.flipkart.ohos.proteus.processor.AttributeProcessor;
import com.flipkart.ohos.proteus.value.Layout;
import com.flipkart.ohos.proteus.value.ObjectValue;
import com.flipkart.ohos.proteus.value.Value;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/**
 * @author adityasharat
 */
public abstract class ViewTypeParser<V extends Component> {
    private static final XMLStreamReader sParser = null;

    public ViewTypeParser parent;

    private AttributeProcessor[] processors = new AttributeProcessor[0];

    private Map<String, AttrSet.Attribute> attributes = new HashMap<>();

    private int offset = 0;

    private AttrSet AttrSet;

    /**
     * getType
     *
     * @return return
     */
    public abstract String getType();

    /**
     * getParentType
     *
     * @return return
     */
    public abstract String getParentType();

    /**
     * createView
     *
     * @param context context
     * @param layout layout
     * @param data data
     * @param parent parent
     * @param dataIndex dataIndex
     * @return return
     */
    public abstract ProteusView createView(ProteusContext context, Layout layout,
                                           ObjectValue data, ComponentContainer parent, int dataIndex);

    /**
     * createViewManager
     *
     * @param context context
     * @param view view
     * @param layout layout
     * @param data data
     * @param caller caller
     * @param parent parent
     * @param dataIndex dataIndex
     * @return return
     */
    public ProteusView.Manager createViewManager(ProteusContext context, ProteusView view,
                                                 Layout layout, ObjectValue data,
                                                 ViewTypeParser caller, ComponentContainer parent,
                                                 int dataIndex) {
        if (null != this.parent && caller != this.parent) {
            return this.parent.createViewManager(context, view, layout, data, caller, parent, dataIndex);
        } else {
            DataContext dataContext = createDataContext(context, layout, data, parent, dataIndex);
            return new ViewManager(context, caller != null ? caller : this, view.getAsView(), layout, dataContext);
        }
    }

    /**
     * createDataContext
     *
     * @param context context
     * @param layout layout
     * @param data data
     * @param parent parent
     * @param dataIndex dataIndex
     * @return return
     */
    protected DataContext createDataContext(ProteusContext context, Layout layout, ObjectValue data,
                                            ComponentContainer parent, int dataIndex) {
        DataContext dataContext, parentDataContext = null;
        Map<String, Value> map = layout.data;

        if (parent instanceof ProteusView) {
            parentDataContext = ((ProteusView) parent).getViewManager().getDataContext();
        }

        if (map == null) {
            if (parentDataContext == null) {
                dataContext = DataContext.create(context, data, dataIndex);
            } else {
                dataContext = parentDataContext.copy();
            }
        } else {
            if (parentDataContext == null) {
                dataContext = DataContext.create(context, data, dataIndex, map);
            } else {
                dataContext = parentDataContext.createChild(context, map, dataIndex);
            }
        }
        return dataContext;
    }

    /**
     * onAfterCreateView
     *
     * @param view view
     * @param parent parent
     * @param dataIndex dataIndex
     */
    public void onAfterCreateView(ProteusView view, ComponentContainer parent, int dataIndex) {
        Component v = view.getAsView();
        if (null == v.getLayoutConfig()) {
            ComponentContainer.LayoutConfig layoutParams;
            if (parent != null) {
                layoutParams = generateDefaultLayoutParams(parent);
            } else {
                layoutParams = new ComponentContainer.LayoutConfig(ComponentContainer.LayoutConfig.MATCH_CONTENT,
                    ComponentContainer.LayoutConfig.MATCH_CONTENT);
            }
            v.setLayoutConfig(layoutParams);
        }
    }

    /**
     * addAttributeProcessors
     */
    protected abstract void addAttributeProcessors();

    /**
     * handleAttribute
     *
     * @param view view
     * @param attributeId attributeId
     * @param value value
     * @return return
     */
    public boolean handleAttribute(V view, int attributeId, Value value) {
        int position = getPosition(attributeId);
        if (position < 0) {
            //noinspection unchecked
            return null != parent && parent.handleAttribute(view, attributeId, value);
        }
        AttributeProcessor attributeProcessor = processors[position];
        //noinspection unchecked
        attributeProcessor.process(view, value);
        return true;
    }

    /**
     * handleChildren
     *
     * @param view view
     * @param children children
     * @return return
     */
    public boolean handleChildren(V view, Value children) {
        //noinspection unchecked
        return null != parent && parent.handleChildren(view, children);
    }

    /**
     * addView
     *
     * @param parent parent
     * @param view view
     * @return return
     */
    public boolean addView(ProteusView parent, ProteusView view) {
        return null != this.parent && this.parent.addView(parent, view);
    }

    /**
     * prepare
     *
     * @param parent parent
     * @param extras extras
     * @return return
     */
    public AttrSet prepare(ViewTypeParser parent, Map<String, AttributeProcessor<V>> extras) {
        this.parent = parent;
        this.processors = new AttributeProcessor[0];
        this.attributes = new HashMap<>();
        this.offset = null != parent ? parent.getAttrSet().getOffset() : 0;
        addAttributeProcessors();
        if (extras != null) {
            addAttributeProcessors(extras);
        }
        this.AttrSet = new AttrSet(attributes.size() > 0 ? attributes : null, null != parent
            ? parent.getAttrSet() : null, processors.length);
        return AttrSet;
    }

    /**
     * getAttributeId
     *
     * @param name name
     * @return return
     */
    public int getAttributeId(String name) {
        AttrSet.Attribute attribute = AttrSet.getAttribute(name);
        return null != attribute ? attribute.id : -1;
    }

    /**
     * getAttrSet
     *
     * @return return
     */
    public AttrSet getAttrSet() {
        return this.AttrSet;
    }

    protected void addAttributeProcessors(Map<String, AttributeProcessor<V>> processors) {
        for (Map.Entry<String, AttributeProcessor<V>> entry : processors.entrySet()) {
            addAttributeProcessor(entry.getKey(), entry.getValue());
        }
    }

    /**
     * addAttributeProcessor
     *
     * @param name name
     * @param processor processor
     */
    protected void addAttributeProcessor(String name, AttributeProcessor<V> processor) {
        addAttributeProcessor(processor);
        attributes.put(name, new AttrSet.Attribute(getAttributeId(processors.length - 1), processor));
    }

    private void addAttributeProcessor(AttributeProcessor<V> handler) {
        processors = Arrays.copyOf(processors, processors.length + 1);
        processors[processors.length - 1] = handler;
    }

    private int getOffset() {
        return offset;
    }

    private int getPosition(int attributeId) {
        return attributeId + getOffset();
    }

    private int getAttributeId(int position) {
        return position - getOffset();
    }

    private ComponentContainer.LayoutConfig generateDefaultLayoutParams(ComponentContainer parent) {
        synchronized (ViewTypeParser.class) {
            initializeAttrSet(parent);
        }
        //return parent.createLayoutConfig(sParser);
        return null;
    }

    private void initializeAttrSet(ComponentContainer parent) {
        //sParser = parent.getResourceManager().getSolidXml(R.layout.layout_params_hack);
        //noinspection StatementWithEmptyBody
        try {
            //noinspection StatementWithEmptyBody
            while (sParser.next() != XMLStreamReader.START_ELEMENT) {
                // Skip everything until the view tag.
            }
        } catch (XMLStreamException e) {
            e.printStackTrace();
        }
    }

    /**
     * @author adityasharat
     */
    public static class AttrSet {
        private final Map<String, Attribute> attributes;

        private final AttrSet parent;

        private final int offset;

        AttrSet(Map<String, Attribute> attributes, AttrSet parent, int offset) {
            this.attributes = attributes;
            this.parent = parent;
            int parentOffset = null != parent ? parent.getOffset() : 0;
            this.offset = parentOffset - offset;
        }

        public Attribute getAttribute(String name) {
            Attribute attribute = null != attributes ? attributes.get(name) : null;
            if (null != attribute) {
                return attribute;
            } else if (null != parent) {
                return parent.getAttribute(name);
            } else {
                return null;
            }
        }

        int getOffset() {
            return offset;
        }

        public static class Attribute {
            public final int id;

            public final AttributeProcessor processor;

            Attribute(int id, AttributeProcessor processor) {
                this.processor = processor;
                this.id = id;
            }
        }
    }
}
